package com.settlement.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.settlement.system.common.constant.SystemConstants;
import com.settlement.system.common.enums.*;
import com.settlement.system.common.util.DateUtils;
import com.settlement.system.common.util.SecurityUtils;
import com.settlement.system.mapper.SysAuditCheckInMapper;
import com.settlement.system.model.entity.*;
import com.settlement.system.model.form.CheckInForm;
import com.settlement.system.model.vo.RulerCheckResult;
import com.settlement.system.model.vo.SysAuditCheckInVo;
import com.settlement.system.service.*;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 核销登记表 服务实现类
 * </p>
 *
 * @author huangrensen
 * @since 2023-08-30
 */
@Service
@RequiredArgsConstructor
public class SysAuditCheckInServiceImpl extends ServiceImpl<SysAuditCheckInMapper, SysAuditCheckIn> implements SysAuditCheckInService {

    private final Logger logger = LoggerFactory.getLogger(SysAuditCheckInServiceImpl.class);

    private final RedissonClient redissonClient;
    private final SysAuditService sysAuditService;
    private final SysDoneAuditService doneAuditService;

    private final AuditErrorRecordService auditErrorRecordService;

    private final SysRoleService sysRoleService;

    private final SysLossService sysLossService;

    private final SysUserService sysUserService;

    private final SysAuditRuleService auditRuleService;

    private final SysBillService sysBillService;

    private final SysDoneAuditNoLinkService doneAuditNoLinkService;

    private final SnowflakeIdWorker snowflakeIdWorker = SnowflakeIdWorker.getInstance();

    @Value("${system.config.expire}")
    private Integer expire;

    @Override
    public boolean saveCheckIn(CheckInForm checkInForm) {
        // 判断是否是自认损失，前后端都判断是否能自认损失
        Set<String> roles = SecurityUtils.getRoles();
        if (Objects.equals(OwnerLossEnum.YES.getValue(), checkInForm.getOwnerLoss())) {
            Assert.isTrue(roles.contains(checkInForm.getRoleCode()) || !StringUtils.hasText(checkInForm.getRoleCode()), "责任方与您的角色身份不同，不能自认损失");
        } else {
            Assert.isTrue(StringUtils.hasText(checkInForm.getRoleCode()), "必须选择一个责任方进行处理");
        }
        SysAuditCheckIn checkIn = new SysAuditCheckIn();
        BeanUtil.copyProperties(checkInForm, checkIn, "id");
        // 当前操作人
        checkIn.setCheckUserId(SecurityUtils.getUserId());
        // 当前操作时间
        checkIn.setCreateTime(LocalDateTime.now());
        checkIn.setOrderStatusName(CommonEnum.DEAL_SUCCESS.getValue());
        if (checkInForm.getOwnerLoss() == null || Objects.equals(OwnerLossEnum.NO.getValue(),
                checkInForm.getOwnerLoss())) {
            // 如果不是自认损失，将自认损失金额改成空
            checkIn.setOwnerLossMoney(BigDecimal.ZERO);
        }
        // 保存一个登记记录
        this.saveOrUpdate(checkIn);
        // 如果是自认损失，需要上级审核
        SysAudit sysAudit = sysAuditService.getBaseMapper().selectOne(new LambdaQueryWrapper<SysAudit>().eq(SysAudit::getOrderNo, checkInForm.getOrderNo()));
        if (checkInForm.getOwnerLoss() != null && Objects.equals(OwnerLossEnum.YES.getValue(), checkInForm.getOwnerLoss())) {
            // 并且需要生成一个角色负责人审核的待办
            sysAudit.setManager(AuditManagerEnum.YES.getValue());
            sysAuditService.saveOrUpdate(sysAudit);
        } else {
            // 不是自认损失，需要把当前的核销单转到对应负责角色
            sysAudit.setBelongRole(checkIn.getRoleCode());
            sysAudit.setManager(AuditManagerEnum.NO.getValue());
            sysAuditService.saveOrUpdate(sysAudit);
        }
        // 保存一个已办记录
        SysDoneAudit sysDoneAudit = doneAuditService.getOne(new LambdaQueryWrapper<SysDoneAudit>()
                .eq(SysDoneAudit::getCreateBy, SecurityUtils.getUserId())
                .eq(SysDoneAudit::getOrderNo, sysAudit.getOrderNo()));
        if (sysDoneAudit == null) {
            sysDoneAudit = new SysDoneAudit();
            BeanUtil.copyProperties(sysAudit, sysDoneAudit, "id");
            sysDoneAudit.setId(snowflakeIdWorker.nextId(""));
            sysDoneAudit.setCreateBy(SecurityUtils.getUserId());
            sysDoneAudit.setCreateTime(LocalDateTime.now());
            sysDoneAudit.setStatus(DoneAuditStatusEnum.DONE.getValue());
        } else {
            BeanUtil.copyProperties(sysAudit, sysDoneAudit, "id");
            sysDoneAudit.setStatus(DoneAuditStatusEnum.DONE.getValue());
        }
        doneAuditService.saveOrUpdate(sysDoneAudit);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean confirm(CheckInForm checkInForm) {
        SysAuditCheckIn checkIn = new SysAuditCheckIn();
        BeanUtil.copyProperties(checkInForm, checkIn, "id");
        checkIn.setOwnerLoss(OwnerLossEnum.YES.getValue());
        // 当前操作人
        checkIn.setCheckUserId(SecurityUtils.getUserId());
        // 当前操作时间
        checkIn.setCreateTime(LocalDateTime.now());
        checkIn.setOrderStatusName(CommonEnum.DEAL_SUCCESS.getValue());
        // 保存一个登记记录
        this.saveOrUpdate(checkIn);
        // 查找出记录
        SysAudit sysAudit = sysAuditService.getBaseMapper().selectOne(new LambdaQueryWrapper<SysAudit>().eq(SysAudit::getOrderNo, checkInForm.getOrderNo()));
        // 获取锁对象
        RLock lock = redissonClient.getLock(SystemConstants.AUDIT_LOCK_PREFIX + sysAudit.getLinkNo());
        if (Objects.isNull(expire)) {
            expire = 30;
        }
        try {
            boolean lockResult = lock.tryLock(20, expire, TimeUnit.SECONDS); // 获取锁失败，需要记录失败日志，方便后面重试
            if (lockResult) {
                logger.error("获取锁成功：{}", sysAudit.getLinkNo());
                this.checkInAudit(sysAudit, checkInForm);
            } else {
                logger.error("获取锁失败：{}", sysAudit.getLinkNo());
                // 获取锁超时
                AuditErrorRecord auditErrorRecord = new AuditErrorRecord();
                // 标准订单
                auditErrorRecord.setDataType(AuditErrorRecordTypeEnum.AUDIT.getValue());
                auditErrorRecord.setDataId(sysAudit.getId());
                auditErrorRecord.setRemark("获取锁超时");
                auditErrorRecordService.checkAndSave(auditErrorRecord);
            }
        } catch (Exception e) {
            logger.error("可能是获取锁失败造成的", e);
            AuditErrorRecord auditErrorRecord = new AuditErrorRecord();
            // 标准订单
            auditErrorRecord.setDataType(AuditErrorRecordTypeEnum.AUDIT.getValue());
            auditErrorRecord.setDataId(sysAudit.getId());
            auditErrorRecord.setRemark("系统异常：%s".formatted(e.getMessage()));
            auditErrorRecordService.checkAndSave(auditErrorRecord);
        } finally {
            if (Objects.nonNull(lock) && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return true;
    }

    private void checkInAudit(SysAudit sysAudit, CheckInForm checkInForm) {
        // 判断是否需要生成财务调账金额 或 自认损失金额 的标准账单
        if (sysAudit.getOwnerLossAmount() != null && BigDecimal.ZERO.compareTo(sysAudit.getOwnerLossAmount()) != 0) {
            // 自认损失金额 的标准账单
            SysBill bill = new SysBill();
            bill.setOriginalBillProject(SystemConstants.OWNER_LOSS);
            bill.setBillAmount(sysAudit.getOwnerLossAmount());
            // 关联单号
            bill.setLinkNo(sysAudit.getLinkNo());
            bill.setBusinessDate(sysAudit.getBusinessDate());
            bill.setSysBillDate(DateUtils.formatDate(sysAudit.getBusinessDate()));
            bill.setTableName("sys_bill_%s".formatted(DateUtils.formatDate(sysAudit.getBusinessDate())));
            // 关联核销单ID
            bill.setAuditId(sysAudit.getId());
            // 保存自认损失
            sysBillService.generateOtherBill(bill, sysAudit.getPlatform(), sysAudit.getStoreId(), 1);
        }
        // 自认损失=应收账款余额
        sysAudit.setOwnerLossAmount(checkInForm.getOwnerLossMoney());
        // 重新计算应收账款余额
        sysAudit.sumReceiveBillAmount();
        // 查出36条核销规则，按照优先级排序
        List<SysAuditRule> ruleList = auditRuleService.getAll();
        // 重新执行规则判断
        RulerCheckResult rulerCheckResult = sysAuditService.rulerCheck(ruleList, sysAudit);
        // 判断是否归档
        if (rulerCheckResult.isConfirm()) {
            // 查出所有参与过当前核销单处理的已办核销单归档
            List<SysDoneAudit> sysDoneAuditList = doneAuditService.list(new LambdaQueryWrapper<SysDoneAudit>().eq(SysDoneAudit::getOrderNo, sysAudit.getOrderNo()));
            List<SysDoneAuditNoLink> doneAuditNoLinks = new ArrayList<>();
            SysDoneAuditNoLink doneAuditNoLink;
            for (SysDoneAudit sysDoneAudit : sysDoneAuditList) {
                // 归档状态
                sysDoneAudit.setStatus(DoneAuditStatusEnum.FILE.getValue());
                sysDoneAudit.setOwnerLoss(OwnerLossEnum.YES.getValue());
                sysDoneAudit.setReceiveBillAmount(BigDecimal.ZERO);
                doneAuditService.updateById(sysDoneAudit);
                doneAuditNoLink = new SysDoneAuditNoLink();
                doneAuditNoLink.setDoneId(sysDoneAudit.getId());
                doneAuditNoLink.setAuditId(sysAudit.getId());
                doneAuditNoLinks.add(doneAuditNoLink);
                try {
                    doneAuditNoLinkService.saveBatch(doneAuditNoLinks);
                } catch (Exception ignore) {
                }
            }
            // 判断当前登录人是否已经有记录，有则更新，否则保存一条已办记录
            SysDoneAudit record = doneAuditService.getOne(new LambdaQueryWrapper<SysDoneAudit>()
                    .eq(SysDoneAudit::getCreateBy, SecurityUtils.getUserId())
                    .eq(SysDoneAudit::getOrderNo, sysAudit.getOrderNo()));
            if (record == null) {
                // 新增
                record = new SysDoneAudit();
                record.setId(snowflakeIdWorker.nextId(""));
                BeanUtil.copyProperties(sysAudit, record, "id", "createBy");
            } else {
                // 覆盖
                BeanUtil.copyProperties(sysAudit, record, "id", "createBy");
                record.setUpdateTime(null);
            }
            record.setStatus(DoneAuditStatusEnum.FILE.getValue());
            record.setCreateBy(SecurityUtils.getUserId());
            doneAuditService.saveOrUpdate(record);
            // 删除待办
            sysAuditService.getBaseMapper().deleteById(sysAudit);
        }
    }

    @Override
    public boolean reject(CheckInForm checkInForm) {
        SysAuditCheckIn checkIn = new SysAuditCheckIn();
        BeanUtil.copyProperties(checkInForm, checkIn, "id");
        // 当前操作人
        checkIn.setCheckUserId(SecurityUtils.getUserId());
        // 当前操作时间
        checkIn.setCreateTime(LocalDateTime.now());
        checkIn.setOrderStatusName(CommonEnum.REJECT.getValue());
        // 保存一个登记记录
        this.saveOrUpdate(checkIn);
        // 如果是自认损失，需要上级审核
        SysAudit sysAudit = sysAuditService.getBaseMapper().selectOne(new LambdaQueryWrapper<SysAudit>().eq(SysAudit::getOrderNo, checkInForm.getOrderNo()));
        // 重新回归到待办列表
        sysAudit.setManager(AuditManagerEnum.NO.getValue());
        sysAuditService.saveOrUpdate(sysAudit);
        return true;
    }

    @Override
    public List<SysAuditCheckInVo> getHistory(String orderNo) {
        List<SysAuditCheckInVo> vos = new ArrayList<>();
        List<SysAuditCheckIn> auditCheckIns = this.list(new LambdaQueryWrapper<SysAuditCheckIn>().eq(SysAuditCheckIn::getOrderNo, orderNo).orderByDesc(SysAuditCheckIn::getUpdateTime));
        if (auditCheckIns.size() > 0) {
            List<SysLoss> sysLossList = sysLossService.list(new LambdaQueryWrapper<SysLoss>().in(SysLoss::getLossCode,
                    auditCheckIns.stream().map(SysAuditCheckIn::getLossReason).toList()));
            List<SysRole> sysRoleList = sysRoleService.list();
            List<SysUser> sysUserList = sysUserService.list(new LambdaQueryWrapper<SysUser>().in(SysUser::getId, auditCheckIns.stream().map(SysAuditCheckIn::getCheckUserId).toList()));
            for (SysAuditCheckIn auditCheckIn : auditCheckIns) {
                SysAuditCheckInVo vo = new SysAuditCheckInVo();
                BeanUtil.copyProperties(auditCheckIn, vo);
                sysUserList.stream().filter(sysUser -> sysUser.getId().equals(auditCheckIn.getCheckUserId()))
                        .findFirst()
                        .ifPresent(sysUser -> vo.setCheckUserName(sysUser.getNickname()));
                sysRoleList.stream().filter(sysRole -> sysRole.getCode().equals(auditCheckIn.getRoleCode()))
                        .findFirst()
                        .ifPresent(sysRole -> vo.setRoleName(sysRole.getName()));
                sysLossList.stream().filter(sysLoss -> sysLoss.getLossCode().equals(auditCheckIn.getLossReason()))
                        .findFirst().ifPresent(sysLoss -> vo.setLossReasonName(sysLoss.getLossName()));
                sysLossList.stream().filter(sysLoss -> sysLoss.getDetailCode().equals(auditCheckIn.getLossDetailName()))
                        .findFirst().ifPresent(sysLoss -> vo.setLossDetailText(sysLoss.getDetailName()));
                vo.setOwnerLossName(OwnerLossEnum.YES.getValue().equals(auditCheckIn.getOwnerLoss()) ? OwnerLossEnum.YES.getLabel() : OwnerLossEnum.NO.getLabel());
                vo.setOrderStatusName(CommonEnum.valueOf(vo.getOrderStatusName()).getLabel());
                vos.add(vo);
            }
        }
        return vos;
    }
}
